/****************************************************************************
 *
 * Copyright (C) 2003-2010 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse.dataset.tippingpoint;

import java.sql.Timestamp;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Vector;

import nfse.NetFSE;
import nfse.NetFSERecord;
import nfse.NetFSEUtil;
import nfse.gui.record.FlexRecordField;
import nfse.gui.server.GUIRecordFactory;
import nfse.gui.server.RecordBuilderUtil;

public class TippingPointRecordFactory extends GUIRecordFactory<TippingPointRecord> {

    public TippingPointRecordFactory() {
        builder = new RecordBuilderUtil();
        builder.addColumn(null, "id", RecordBuilderUtil.BIGINT);
        RecordBuilderUtil.SQLField field = builder.addColumn("Start TS", "startts", RecordBuilderUtil.TIMESTAMP);
        field = builder.addColumn("Src IP", "srcip", RecordBuilderUtil.INET);
        field = builder.addColumn("Src Port", "srcport", RecordBuilderUtil.INTEGER);
        field.setSubtype(FlexRecordField.SUB_TYPE_PORT);
        field = builder.addColumn("Dst IP", "dstip", RecordBuilderUtil.INET);
        field = builder.addColumn("Dst Port", "dstport", RecordBuilderUtil.INTEGER);
        field.setSubtype(FlexRecordField.SUB_TYPE_PORT);
        field = builder.addColumn("Protocol", "protocol", RecordBuilderUtil.INTEGER);
        field.setSubtype(FlexRecordField.SUB_TYPE_PROTOCOL);
        field = builder.addColumn("Action", "action", RecordBuilderUtil.VARCHAR, 50);
        field = builder.addColumn("Alert Type", "alerttype", RecordBuilderUtil.VARCHAR, 50);
        field = builder.addColumn("Description", "alertdesc", RecordBuilderUtil.VARCHAR, 50);
        field = builder.addColumn("Severity", "severity", RecordBuilderUtil.VARCHAR, 10);
    }

    @Override
    protected void setValues(TippingPointRecord record, int id) {
        int idx = -1;
        builder.setFieldValue(++idx, "" + id);
        builder.setFieldValue(++idx, "" + record.ts1);
        builder.setFieldValue(++idx, record.getSrcAddr());
        builder.setFieldValue(++idx, "" + record.srcPort);
        builder.setFieldValue(++idx, record.getDstAddr());
        builder.setFieldValue(++idx, "" + record.dstPort);
        builder.setFieldValue(++idx, "" + record.protocol);
        builder.setFieldValue(++idx, record.action);
        builder.setFieldValue(++idx, record.alertType);
        builder.setFieldValue(++idx, record.alertDesc);
        builder.setFieldValue(++idx, record.severity);

    }

    /***************************************************************************
     * Given a raw TippingPoint record, parse according to the 3rd log format.
     * 
     * @param line
     *            The log to parse into an object
     * @return The data structure for this log record or null on error
     * @throws Exception 
     */
    private TippingPointRecord generateV4(String[] tokens, String line, int sensor) throws Exception {
        try {
            TippingPointRecord tp = new TippingPointRecord();
            tp.setSensorID(sensor);
            tp.setTypeName(typeName);
            tp.setTypeID(typeID);
            tp.setInitialString(line);

            int offset = 0;
            if (tokens.length == 22) {
                offset = 1;
            } else if (tokens.length != 21) {
                return null;
            }

            String[] header = tokens[0 + offset].split(" ");
            if (header[header.length - 1].trim().equals("P2P")) {
                tp.p2p = true;
            }

            // Timestamp format is YYYYMMDDTHHMMSS+_TZ_
            String tsStr = tokens[16 + offset].substring(0, 4) + "-";
            tsStr += tokens[16 + offset].substring(4, 6) + "-";
            tsStr += tokens[16 + offset].substring(6, 8) + " ";
            tsStr += tokens[16 + offset].substring(9, 11) + ":";
            tsStr += tokens[16 + offset].substring(11, 13) + ":";
            tsStr += tokens[16 + offset].substring(13, 15);
            tp.ts1 = Timestamp.valueOf(tsStr);

            // Process the ip:port pairs
            tp.setSrcAddr(tokens[12+offset]);
            tp.srcPort = Integer.parseInt(tokens[13+offset]);
            tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(tokens[14+offset]), 16);
            tp.dstPort = Integer.parseInt(tokens[15+offset]);

            tp.action = tokens[6 + offset].trim();
            tp.severity = tokens[7 + offset].trim();
            tp.protocol = tokens[11 + offset].trim();
            tp.sensorName = tokens[3 + offset];
            tp.type = 3;

            String[] tokens2 = tokens[9 + offset].split(":");
            tp.alertID = Integer.parseInt(tokens2[0].trim());
            tp.alertType = tokens2[1].trim();
            tp.alertDesc = tokens2[2].trim();
            tp.policyUUID = tokens[8 + offset];

            return tp;
        } catch (Exception E) {
            if (line.indexOf("Nimda Attack") < 0) {
                // Only show error info for non-Nimda blocks
                throw(E);
            }
        }
        return null;
    }
    
    /***************************************************************************
     * Given a raw TippingPoint record, parse according to the 3rd log format.
     * 
     * @param line
     *            The log to parse into an object
     * @return The data structure for this log record or null on error
     * @throws Exception 
     */
    private TippingPointRecord generateV3(String line, int sensor) throws Exception {
        try {
            TippingPointRecord tp = new TippingPointRecord();
            String[] tokens = line.split("\t");
            tp.setSensorID(sensor);
            tp.setTypeName(typeName);
            tp.setTypeID(typeID);
            tp.setInitialString(line);
            
            if (tokens[0].indexOf("tipping-ipa") == -1) {
                // Return null for any logs from SMS (i.e. not from the IPA)
                return null;
            }
            
            int offset = 0;
            if (tokens.length > 20) {
                return generateV4(tokens, line, sensor);
            } else if (tokens.length == 20) {
                offset = 1;
            } else if (tokens.length != 19) {
                return null;
            }

            String[] header = tokens[0 + offset].split(" ");
            if (header[header.length - 1].trim().equals("P2P")) {
                tp.p2p = true;
            }

            // Timestamp format is YYYYMMDDTHHMMSS+_TZ_
            String tsStr = tokens[14 + offset].substring(0, 4) + "-";
            tsStr += tokens[14 + offset].substring(4, 6) + "-";
            tsStr += tokens[14 + offset].substring(6, 8) + " ";
            tsStr += tokens[14 + offset].substring(9, 11) + ":";
            tsStr += tokens[14 + offset].substring(11, 13) + ":";
            tsStr += tokens[14 + offset].substring(13, 15);
            tp.ts1 = Timestamp.valueOf(tsStr);

            // Process the ip:port pairs
            String[] tokens2 = tokens[12 + offset].split(":");
            tp.setSrcAddr(tokens2[0]);
            tp.setSrcIP(Long.parseLong(NetFSEUtil.convertIPtoHex(tp.getSrcAddr()), 16));
            tp.srcPort = Integer.parseInt(tokens2[1]);
            tokens2 = tokens[13 + offset].split(":");
            tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(tokens2[0]), 16);
            tp.dstPort = Integer.parseInt(tokens2[1]);

            tp.action = tokens[6 + offset].trim();
            tp.severity = tokens[7 + offset].trim();
            tp.protocol = tokens[11 + offset].trim();
            tp.sensorName = tokens[3 + offset];
            tp.type = 3;

            tokens2 = tokens[9 + offset].split(":");
            tp.alertID = Integer.parseInt(tokens2[0].trim());
            tp.alertType = tokens2[1].trim();
            tp.alertDesc = tokens2[2].trim();
            tp.policyUUID = tokens[8 + offset];

            return tp;
        } catch (Exception E) {
            if (line.indexOf("Nimda Attack") < 0) {
                throw(E);
            }
        }
        return null;
    }

    /***************************************************************************
     * Given a raw TippingPoint record, parse according to the 1st or 2nd log
     * format.
     * 
     * @param line
     *            The log to parse into an object
     * @return The data structure for this log record or null on error
     */
    @Override
    public TippingPointRecord generate(String line, int sensor) {
        try {
            TippingPointRecord tp = new TippingPointRecord();
            tp.setTypeID(typeID);
            tp.setTypeName(typeName);
            tp.setInitialString(line);
            line = line.replace('\'', ' ').replace(',', ' ').replace('"', ' ');
            String[] tokens = line.split("\\^I");

            // System.out.println("# tokens = " + tokens.length);

            // If there is only one token then the record is in the 3rd log
            // format
            if (tokens.length == 1)
                return generateV3(line, sensor);

            if (tokens[0].indexOf("tipping-ipa") == -1) {
                // Return null for any logs from SMS (i.e. not from the IPA)
                return null;
            } else {
                for (int i = 0; i < tokens.length; i++)
                    tokens[i] = tokens[i].trim();
            }

            tp.setSensorID(sensor);
            if (tokens.length == 19) { // The 2nd syslog format

                tp.type = 2;
                tp.version = tokens[1];

                // Process the alert timestamp string
                String alertTSStr = tokens[2];
                int year = Integer.parseInt(alertTSStr.substring(0, 4));
                int month = Integer.parseInt(alertTSStr.substring(4, 6));
                int day = Integer.parseInt(alertTSStr.substring(6, 8));
                int hour = Integer.parseInt(alertTSStr.substring(9, 11));
                int minute = Integer.parseInt(alertTSStr.substring(11, 13));
                int second = Integer.parseInt(alertTSStr.substring(13, 15));
                tp.ts1 = Timestamp.valueOf(year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second);

                tp.sensorName = (tokens[3].split(" /"))[0].trim();
                tp.seqNum = Long.parseLong(tokens[4]);
                tp.action = tokens[6];
                tp.severity = tokens[7];
                tp.policyUUID = tokens[8];
                tp.protocol = tokens[11];

                String[] tokens2 = tokens[9].split(": ");

                // Some tokens have three values, others only two
                if (tokens2.length == 3) {
                    tp.alertID = Integer.parseInt(tokens2[0]);
                    tp.alertType = tokens2[1];
                    tp.alertDesc = tokens2[2];
                } else if (tokens2.length == 2) {
                    tp.alertID = Integer.parseInt(tokens2[0]);
                    tp.alertDesc = tokens2[1];
                } else {
                    throw (new Exception("Bad alert info: '" + line + "'"));
                }

                // Process the ip:port pairs for source and destination
                String[] src = tokens[12].split(":");
                String[] dst = tokens[13].split(":");
                tp.setSrcAddr(src[0]);
                tp.srcPort = Integer.parseInt(src[1]);
                tp.dstPort = Integer.parseInt(dst[1]);
                tp.setSrcIP(Long.parseLong(NetFSEUtil.convertIPtoHex(tp.getSrcAddr()), 16));
                tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(dst[0]), 16);

                return tp;
            } else if (tokens.length == 14) { // The 1st syslog format
                tp.type = 1;
                String[] tokens2 = tokens[0].split(" ");

                // Remove extra whitespace from the array
                Vector<String> temp = new Vector<String>();
                for (int i = 0; i < tokens2.length; i++) {
                    if (tokens2[i].trim().length() > 0)
                        temp.addElement(tokens2[i]);
                }
                tokens2 = new String[temp.size()];
                for (int i = 0; i < tokens2.length; i++) {
                    tokens2[i] = (String) temp.elementAt(i);
                }

                if (tokens2.length == 10) {

                    int month = NetFSE.getMonthValue(tokens2[0]);
                    int day = Integer.parseInt(tokens2[1]);
                    GregorianCalendar now = new GregorianCalendar();
                    now.setTimeInMillis(System.currentTimeMillis());
                    int year = now.get(Calendar.YEAR);
                    tp.ts1 = java.sql.Timestamp.valueOf(year + "-" + month + "-" + day + " " + tokens2[2]);
                    tp.sensorName = tokens2[3];
                    tp.action = tokens2[5];
                    month = NetFSE.getMonthValue(tokens2[6]);
                    day = Integer.parseInt(tokens2[7]);

                    tp.ts2 = java.sql.Timestamp.valueOf(year + "-" + month + "-" + day + " " + tokens2[8]);
                    tp.action = tokens2[9];

                    // Misc fields; some are unused and commented out
                    // String val1 = tokens[1];
                    // String val2 = tokens[2];
                    // String category = tokens[3];
                    // String val3 = tokens[4];
                    tp.protocol = tokens[7];
                    // String val4 = tokens[10];
                    // String val5 = tokens[11];
                    // String val6 = tokens[12];
                    // String val7 = tokens[13];

                    tokens2 = tokens[5].split(": ");

                    // Some tokens have three values, others only two
                    if (tokens2.length == 2) {
                        tp.alertID = Integer.parseInt(tokens2[0]);
                        tp.alertType = "";
                        tp.alertDesc = tokens2[1];
                    } else if (tokens2.length == 3) {
                        tp.alertID = Integer.parseInt(tokens2[0]);
                        tp.alertType = tokens2[1];
                        tp.alertDesc = tokens2[2];
                    }

                    // Process the ip:port pairs for source and destination
                    tp.setSrcAddr(tokens[8].substring(0, tokens[8].indexOf(':')));
                    tp.setSrcIP(Long.parseLong(NetFSEUtil.convertIPtoHex(tp.getSrcAddr()), 16));
                    tp.srcPort = Integer.parseInt(tokens[8].substring(tokens[8].indexOf(":") + 1));
                    tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(tokens[9].substring(0, tokens[9].indexOf(':'))), 16);
                    tp.dstPort = Integer.parseInt(tokens[9].substring(tokens[9].indexOf(":") + 1));

                    return tp;
                } else {
                    System.err.println("Invalid number of header fields: " + tokens2.length);
                }
            }

        } catch (Exception E) {
            E.printStackTrace();
        }
        return null;
    }

    @Override
    public NetFSERecord generate() {
        return new TippingPointRecord();
    }

}
